iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
2
Modern Web

Angular 全集中筆記系列 第 7

第 7 型 - 元件互動與雙向資料繫結 (Two-way Binding)

  • 分享至 

  • xImage
  •  

Angular 應用程式主要是利用元件所組合而成,因此開發過程常需要處理元件之間的互動。例如在案例中,對於 AppComponent (父元件) 使用 TaskComponent (子元件) 這種上下層的元件關係,Angular 提供了 @Input()@Output() 兩個裝飾器來處理兩者的互動;另外,Angular 也提供了雙向繫結 (Two-way Binding) 的語法糖,讓元件類別與頁面之間共享資料。這一篇將原在 TaskComponent 內的待辦事項資料移至 AppComponent,來實作兩個元件之間的互動。

利用 @Input() 裝飾器來接收待辦事項資料

@Input() 裝飾器用來定義元件屬性是可從父元件接收值,而在父元件則可以利用屬性繫結 (Property Binding) 來傳入資料。

首先,先將原本在 task.component.ts 中的待辦事項屬性移至 app.component.ts 中。

import { Task } from "./model/task";

export class AppComponent implements OnInit {
  task: Task;

  ngOnInit(): void {
    this.task = new Task("頁面需要顯示待辦事項主旨");
  }
}

接著,在 task.component.ts 定義主旨與狀態的輸入屬性。

export class TaskComponent implements OnInit {
  @Input() subject: string;
  @Input() state: TaskState;

  constructor() {}

  ngOnInit(): void {}
}

最後,在 app.component.html 利用屬性繫結 (Property Bidning) 將待辦事項主旨與狀態傳入 TaskComponent 內。

<app-task [subject]="task.subject" [state]="task.state"></app-task>

是否使用屬性繫結傳入資料,會依需求而定。如果傳入的資料是不會變更的基礎型別資料,那可以直接將值設定至屬性,如 <app-task subject="頁面需要顯示待辦事項主旨" state="TaskState.None"></app-task>,而不需使用屬性繫結。

透過 setter 實作待辦事項狀態文字

原本利用內嵌繫結 (Interpolation) 指定 getStateDesc() 方法來實作狀態文字的顯示;由於已將狀態由外部傳入,因此也可以在 task.component.ts 利用 setter 來監控傳入的狀態值,並判斷與記錄狀態文字。

透過 getter 與 setter 存取屬性值,可以在使用屬性值時,有更高的靈活度。

export class TaskComponent implements OnInit {
  @Input() subject: string;

  private _state: TaskState;
  @Input()
  set state(state: TaskState) {
    this._state = state;
    this.stateDesc = this.getStateDesc();
  }
  get state(): TaskState {
    return this._state;
  }

  stateDesc: string;
}

針對私有屬性名稱前加入底線 (_),是一種變數命名的風格。但在 Angular 專案的 TSLint 規則定義中, 預設不允許此命名方式,而會出現警告訊息,因此需要修改根目錄下的 tslint.json 檔案,在 variable-name.options 屬性內加入 allow-leading-underscore 值。詳細內容可見 TSLint 規則說明

最後,將 task.component.html 檔案中原來繫結 getStateDesc() 方法,變更為 stateDesc 屬性即可。

<div class="card">
  <div class="content">
    <span
      [class.doing]="state === TaskState.Doing"
      [class.finish]="state === TaskState.Finish"
    >
      {{ stateDesc }}
    </span>
  </div>
</div>

為了測試上述程式是否正確,在 app.component.ts 中加入一陣列 (Array),其包含了三種狀態的待辦事項,並透過按鈕來決定要繫結的對象。

export class AppComponent implements OnInit {
  tasks: Task[];

  selectedTask: Task;

  ngOnInit(): void {
    this.tasks = [
      new Task("頁面需要顯示待辦事項主旨"),
      new Task("可以設定待辦事項的狀態", TaskState.Doing),
      new Task("當待辦事項狀態為已完的事項無法編輯事項", TaskState.Finish),
    ];
    this.onSelectTask(0);
  }

  onSelectTask(index: number): void {
    this.selectedTask = this.tasks[index];
  }
}
<div>
  <button type="button" (click)="onSelectTask(0)">未完成事項</button>
  <button type="button" (click)="onSelectTask(1)">工作中事項</button>
  <button type="button" (click)="onSelectTask(2)">已完成事項</button>
</div>
<app-task
  [subject]="selectedTask.subject"
  [state]="selectedTask.state"
></app-task>

另外,也在 app.component.css 檔案加入樣式設定,讓頁面有更明顯的呈現。

div {
  padding: 20px 10px;
}

div button {
  margin-right: 5px;
}

Result

利用生命週期 ngOnChanges() 實作待辦事項狀態文字

當一個元件需要監控多個傳入屬性時,利用 setter 方法會需要較多的處理,如每個屬性都需要一個私有屬性記錄。在 Angular 應用程式中,當傳入參數的值有變更時,會觸發 Angular 生命週期鉤子 (Lifecycle Hook) 中的 ngOnChanges() 方法,並傳入目前與前一次的屬性值。因此這個需求可以改在 task.component.ts 檔案中實作 OnChanges 介面,並在 ngOnChanges() 方法取得狀態文字。

export class TaskComponent implements OnInit, OnChanges {
  @Input() subject: string;
  @Input() state: TaskState;

  stateDesc: string;

  ngOnInit(): void {}

  ngOnChanges(): void {
    this.stateDesc = this.getStateDesc();
  }
}

利用 Output() 裝飾器實作待辦事項狀態變更

由於目前已將待辦事項資料移至 AppComponent,使得在點選 TaskComponent 的狀態變更按鈕時,需要通知 AppComponent 進行變更。透過 Output() 裝飾器可以在子元件內定義一個 EventEmitter 泛型型別的輸出屬性,且利用此屬性的 emit() 方法來將值傳遞資料給父元件。

export class TaskComponent implements OnInit, OnChanges {
  @Input() subject: string;

  @Input() state: TaskState;
  @Output() stateChange = new EventEmitter<TaskState>();

  onSetTaskState(state: TaskState): void {
    this.stateChange.emit(state);
  }
}

task.component.ts 中加入 stateChange 的輸出屬性,並在設定事項狀態的方法中,利用 emit() 方法將狀態傳遞出去。接著,在 app.component.html 透過事件繫結監控 stateChange,進而變更待辦事項的狀態。

<app-task
  [subject]="selectedTask.subject"
  [state]="selectedTask.state"
  (stateChange)="onStateChange($event)"
></app-task>
export class AppComponent implements OnInit {
  onStateChnage(state: TaskState): void {
    this.selectedTask.state = state;
  }
}

Input()Output() 兩個裝飾器可以傳入字別,來變更父元件繫結所使用的名稱。不過,依 Angular 風格指南 ,在元件的定義上並不建議這麼做。

利用雙向繫結實作待辦事項狀態變更

上面程式利用了屬性繫結 (Property Binding) 設定 TaskComponent 中狀態屬性,並利用事件繫結 (Event Binding) 來監控與變更狀態屬性。而針對此種針對資料的設定與監控變更的需求,Angular 提供了一個名為雙向繫結 (Two-way Binding)的語法糖來簡化程式,其語法為 [()](可用盒子裡的香蕉來記憶),是將屬性繫結與事件繫結語法合併而成。

因此,可以在 app.component.html 利用雙向繫結語法綁定 selectedTask.state 屬性,來實作變更待辦事項變更;此時,Angular 會利用 TaskComponent 內的 state 輸入屬性與 stateChange 輸出屬性(此名稱須以 Change 為結尾)來設定與變更待辦事項狀態的資料。

<app-task
  [subject]="selectedTask.subject"
  [(state)]="selectedTask.state"
></app-task>

結論

這篇利用了 Input()Output() 兩個裝飾器,來串連 AppComponent 與 TaskComponent 兩個元件,並使用雙向繫結 (Two-way Binding) 來實作狀態變更的需求,實作的程式碼可至 GitHub 參考。


上一篇
第 6 型 - 單向資料繫結 (One-way Binding)
下一篇
第 8 型 - 屬性型指令 (Attribute Directive)
系列文
Angular 全集中筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言